package com.hserver.docker.service.impl;

import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.command.*;
import com.github.dockerjava.api.model.*;
import com.github.dockerjava.core.DefaultDockerClientConfig;
import com.github.dockerjava.core.DockerClientConfig;
import com.github.dockerjava.core.DockerClientImpl;
import com.github.dockerjava.jaxrs.JerseyDockerHttpClient;
import com.hserver.docker.bean.Deploy;
import com.hserver.docker.bean.Record;
import com.hserver.docker.bean.Registry;
import com.hserver.docker.bean.StatusVo;
import com.hserver.docker.config.ConstConfig;
import com.hserver.docker.service.*;
import com.hserver.docker.utils.DockerUtil;
import cn.hserver.core.ioc.annotation.Autowired;
import cn.hserver.core.ioc.annotation.Bean;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.TimeUnit;

import static com.github.dockerjava.api.model.ExposedPort.tcp;
import static com.github.dockerjava.api.model.HostConfig.newHostConfig;
import static com.hserver.docker.config.ConstConfig.PROJECT_PATH;

/**
 * @author hxm
 */
@Bean
public class DockerServiceImpl implements DockerService {

    @Autowired
    private RecordService recordService;

    @Autowired
    private LogService logService;

    @Autowired
    private DeployService deployService;

    @Autowired
    private ServiceService serviceService;

    @Autowired
    private RegistryService registryService;

    @Override
    public Boolean removeContainer(String uuid) {
        Deploy deploy = deployService.getDeploy(uuid);
        if (deploy != null) {
            DockerClient dockerClient = DockerUtil.getDocker();
            try {
                dockerClient.removeContainerCmd(deploy.getContainerId()).exec();
                return true;
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    dockerClient.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return false;
    }

    @Override
    public Boolean stopContainer(String uuid) {
        Deploy deploy = deployService.getDeploy(uuid);
        if (deploy != null) {
            DockerClient dockerClient = DockerUtil.getDocker();
            try {
                dockerClient.stopContainerCmd(deploy.getContainerId()).exec();
                return true;
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    dockerClient.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return false;
    }

    @Override
    public StatusVo infoContainer(String uuid) {
        Deploy deploy = deployService.getDeploy(uuid);
        if (deploy != null) {
            DockerClient dockerClient = DockerUtil.getDocker();
            try {
                StatusVo statusVo = new StatusVo();
                InspectContainerResponse exec = dockerClient.inspectContainerCmd(deploy.getContainerId()).exec();
                statusVo.setStatus(exec.getState().getStatus().trim());
                statusVo.setImageId(exec.getImageId().trim());
                statusVo.setCreated(exec.getCreated().trim());
                statusVo.setId(exec.getId().trim());
                statusVo.setLogPath(exec.getLogPath().trim());
                return statusVo;
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    dockerClient.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

    @Override
    public Boolean runContainer(String uuid, String... recordId) {
        Deploy deploy = deployService.getDeploy(uuid);
        if (deploy != null) {
            DockerClient dockerClient = DockerUtil.getDocker();
            try {
                dockerClient.startContainerCmd(deploy.getContainerId()).exec();
                if (recordId != null && recordId.length == 1) {
                    logService.add(recordId[0], "容器启动成功-" + deploy.getContainerId());
                }
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                if (recordId != null && recordId.length == 1) {
                    logService.add(recordId[0], "容器启动失败-" + e.getMessage());
                }
            } finally {
                try {
                    dockerClient.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return false;
    }


    @Override
    public Boolean runRollBackContainer(String uuid, String recordId) {
        Deploy deploy = deployService.getDeploy(uuid);
        if (deploy.getSize() != null && deploy.getSize() > 0) {
            serviceService.createService(deploy.getImageTag(), deploy.getSize(), uuid, recordId);
        } else {
            createContainer(uuid, recordId);
            runContainer(uuid, recordId);
        }
        return null;
    }

    @Override
    public Boolean createContainer(String uuid, String recordId) {
        DockerClient dockerClient = DockerUtil.getDocker();
        try {
            Deploy deploy = deployService.getDeploy(uuid);
            Record record = recordService.getById(recordId);
            if (deploy != null && record != null) {
                if (deploy.getContainerId() != null) {
                    //停止容器
                    try {
                        dockerClient.stopContainerCmd(deploy.getContainerId()).exec();
                        logService.add(recordId, "停止容器-" + deploy.getContainerId());
                    } catch (Exception e1) {
                        e1.printStackTrace();
                    }
                    //删除容器
                    try {
                        dockerClient.removeContainerCmd(deploy.getContainerId()).exec();
                        logService.add(recordId, "删除容器-" + deploy.getContainerId());
                    } catch (Exception e2) {
                        e2.printStackTrace();
                    }
                }
                //创建一个容器
                logService.add(recordId, "创建一个新的容器");
                try {
                    //看看是否有端口映
                    Ports portBindings = new Ports();
                    List<ExposedPort> exposedPorts = new ArrayList<>();
                    String ports = deploy.getPorts();
                    if (ports != null && ports.trim().length() > 0) {
                        String[] split = ports.split(",");
                        for (String s : split) {
                            String[] port = s.split(":");
                            if (port.length == 2) {
                                try {
                                    //主机的端口
                                    int i = Integer.parseInt(port[0]);
                                    //容器的端口
                                    int j = Integer.parseInt(port[1]);
                                    //容器端口
                                    ExposedPort tcp = tcp(j);
                                    exposedPorts.add(tcp);
                                    //主机端口
                                    portBindings.bind(tcp, Ports.Binding.bindPort(i));
                                } catch (Exception e) {
                                    logService.add(recordId, "端口转换异常-" + e.getMessage());
                                }
                            }
                        }
                    }
                    String volumePath = deploy.getVolume();
                    List<Bind> volumes = new ArrayList<>();
                    if (volumePath != null && volumePath.trim().length() > 0) {
                        String[] split = volumePath.split(",");
                        for (String s : split) {
                            String[] vo = s.split(":");
                            //主机地址,容器地址
                            volumes.add(new Bind(vo[0].trim(), new Volume(vo[1].trim())));
                        }
                    }
                    CreateContainerResponse exec = dockerClient
                            .createContainerCmd(record.getImageId())
                            .withExposedPorts(exposedPorts)
                            .withHostConfig(newHostConfig()
                                    .withPortBindings(portBindings)
                                    .withRestartPolicy(RestartPolicy.alwaysRestart())
                                    .withBinds(volumes))
                            .exec();
                    //容器ID入库
                    deployService.updateContainerId(uuid, exec.getId());
                    logService.add(recordId, "容器创建完成 容器ID-" + exec.getId());
                    return true;
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                logService.add(recordId, "容器创建失败 deploy:" + deploy + "record" + record);
            }
        } catch (Exception e) {
            logService.add(recordId, "容器创建失败" + e.getMessage());
            e.printStackTrace();
        } finally {
            try {
                dockerClient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return false;
    }

    @Override
    public Boolean buildImage(Deploy deploy, String recordId) {
        DockerClient dockerClient = DockerUtil.getDocker();
        try {
            BuildImageCmd buildImageCmd = dockerClient
                    .buildImageCmd()
                    .withNoCache(true)
                    .withBaseDirectory(new File(PROJECT_PATH + deploy.getUuid()));
            if (deploy.getDockerFilePath().startsWith("abs:")) {
                File file = new File(deploy.getDockerFilePath().replace("abs:", "") + deploy.getDockerFileName());
                if (file.isFile()) {
                    File newFile = new File(PROJECT_PATH + deploy.getUuid() + File.separator + deploy.getDockerFileName());
                    if (newFile.exists()) {
                        newFile.delete();
                    }
                    Files.copy(file.toPath(), newFile.toPath());
                    buildImageCmd.withDockerfile(newFile);
                } else {
                    return false;
                }
            } else {
                buildImageCmd.withDockerfile(new File(PROJECT_PATH + deploy.getUuid() + deploy.getDockerFilePath() + deploy.getDockerFileName()));
            }
            DockerBuildImageResultCallback imageResultCallback = buildImageCmd.withTags(new HashSet<>(Collections.singletonList(deploy.getImageTag())))
                    .exec(new DockerBuildImageResultCallback(recordId));
            logService.add(recordId, "上传代码到Docker中");
            String s = imageResultCallback.awaitImageId(5, TimeUnit.HOURS);
            logService.add(recordId, "镜像构建完成");
            if (deploy.getSize() != null) {
                Registry registry = registryService.getRegistry();
                logService.add(recordId, "推送到私有仓库中");
                String s1 = registry.getHost() + ":" + registry.getPort() + "/" + deploy.getImageTag().split(":")[0];
                String s2 = deploy.getImageTag().split(":")[1];
                dockerClient.tagImageCmd(deploy.getImageTag(), s1, s2).exec();
                boolean b = dockerClient.pushImageCmd(s1)
                        .withTag( s2)
                        .start()
                        .awaitCompletion(5, TimeUnit.HOURS);
                if (b) {
                    logService.add(recordId, "推送到私有仓库完成");
                } else {
                    logService.add(recordId, "推送到私有仓库失败");
                }
            }
            recordService.updateImageId(recordId, s);
        } catch (Exception e) {
            logService.add(recordId, "构建错误");
            logService.add(recordId, e.getMessage());
            return false;
        } finally {
            try {
                dockerClient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return true;
    }

    @Override
    public Boolean removeImage(String recordId) {
        DockerClient dockerClient = DockerUtil.getDocker();
        try {
            Record record = recordService.getById(recordId);
            if (record != null && record.getImageId() != null) {
                String imageId = record.getImageId();
                dockerClient.removeImageCmd(imageId).exec();
                return true;
            }
        } finally {
            try {
                dockerClient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return false;
    }

    class DockerBuildImageResultCallback extends BuildImageResultCallback {

        private String recordId;

        DockerBuildImageResultCallback(String recordId) {
            this.recordId = recordId;
        }

        @Override
        public void onNext(BuildResponseItem item) {
            super.onNext(item);
            String stream = item.getStream();
            if (stream != null && stream.trim().length() > 0) {
                logService.add(recordId, stream.trim());
            }
        }

        @Override
        public void onError(Throwable throwable) {
            super.onError(throwable);
            logService.add(recordId, "构建错误");
            logService.add(recordId, throwable.getMessage());
        }
    }


    @Override
    public Boolean checkDockerUrl(String url) {
        try {
            DockerClientConfig config = DefaultDockerClientConfig.createDefaultConfigBuilder()
                    .withDockerHost(url)
                    .build();
            JerseyDockerHttpClient httpClient = new JerseyDockerHttpClient.Builder().dockerHost(config.getDockerHost()).build();
            DockerClient instance = DockerClientImpl.getInstance(config, httpClient);
            Version exec = instance.versionCmd().exec();
            if (exec != null) {
                return true;
            }
        } catch (Exception e) {
        }
        return false;
    }

    @Override
    public void removeImageId(String imageId) throws Exception {
        DockerClient dockerClient = DockerUtil.getDocker();
        try {
            dockerClient.removeImageCmd(imageId).exec();
        } finally {
            dockerClient.close();
        }
    }


    @Override
    public List<Container> containerList() {
        DockerClient docker = DockerUtil.getDocker();
        try {
            return docker.listContainersCmd().withShowAll(true).exec();
        } finally {
            try {
                docker.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public List<Image> imagesList() {
        DockerClient docker = DockerUtil.getDocker();
        try {
            return docker.listImagesCmd().exec();
        } finally {
            try {
                docker.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void runContainerId(String containerId) throws Exception {
        DockerClient docker = DockerUtil.getDocker();
        try {
            docker.startContainerCmd(containerId).exec();
        } finally {
            docker.close();
        }
    }

    @Override
    public void stopContainerId(String containerId) throws Exception {
        DockerClient docker = DockerUtil.getDocker();
        try {
            docker.stopContainerCmd(containerId).exec();
        } finally {
            docker.close();
        }
    }

    @Override
    public void removeContainerId(String containerId) throws Exception {
        DockerClient docker = DockerUtil.getDocker();
        try {
            docker.removeContainerCmd(containerId).exec();
        } finally {
            docker.close();
        }
    }

}
